/*
 * JSParams.js
 * Parameters for Barreto-Naehrig (BN) pairing-friendly elliptic curves.
 * Copyright (C) Jorge H. C. Lucema.
 */

function JSParams(fieldSize) {
    this._0 = new BigInteger("0");
    this._1 = new BigInteger("1");
    this._2 = new BigInteger("2");
    this._3 = new BigInteger("3");
    this._4 = new BigInteger("4");
    this._6 = new BigInteger("6");
    this._9 = new BigInteger("9");
    this.b = 3;
    switch(fieldSize) {
        case 192:
            this.u = new BigInteger("-11000000000000000000010010000000000010000000001", 2);
            break;
        case 224:
            this.u = new BigInteger("-1100000100000000000000000010000000100000000000000000001", 2);
            break;
        case 254:
            this.b = 2;
            this.u = new BigInteger("-100000010000000000000000000000000000000000000000000000000000001", 2);
            break;
        case 256:
            this.u = new BigInteger("110000010000000000000000000000000000000000001000000000001000011", 2);
            break;
        default:
            throw new Error("The specified parameters do not properly define a suitable BN curve");
    }
    this.p = this.u.add(this._1).multiply(this._6.multiply(this.u)).add(this._4).multiply(this.u).add(this._1).multiply(this._6.multiply(this.u)).add(this._1);
    this.t = this._6.multiply(this.u).multiply(this.u).add(this._1);
    this.r = this.p.add(this._1).subtract(this.t);
    this.zeta = this._9.multiply(this.u).multiply(this.u.shiftLeft(1).multiply(this.u.add(this._1)).add(this._1)).add(this._1);
}

/*
 * Compute the quadratic character of v
 * @param {BigInteger} v
 * @returns J
 */

function legendre(v) {
    var J = 1;
    var x = new BigInteger(v);
    var y = this.p;
    if (x.signum() < 0) {
        x = x.negate();
        if (y.testBit(0) && y.testBit(1)) { // y = 3 (mod 4)
            J = -J;
        }
    }
    while (y.compareTo(this._1) > 0) {
        x = x.mod(y);
        if (x.compareTo(y.shiftRight(1)) > 0) {
            x = y.subtract(x);
            if (y.testBit(0) && y.testBit(1)) { // y = 3 (mod 4)
                J = -J;
            }
        }
        if (x.signum() === 0) {
            x = this._1;
            y = this._0;
            J = 0;
            break;
        }
        while (!x.testBit(0) && !x.testBit(1)) { // 4 divides x
            x = x.shiftRight(2);
        }
        if (!x.testBit(0)) { // 2 divides x
            x = x.shiftRight(1);
            if (y.testBit(0) && (y.testBit(1) === !y.testBit(2))) { // y = �3 (mod 8)
                J = -J;
            }
        }
        if (x.testBit(0) && x.testBit(1) && y.testBit(0) && y.testBit(1)) { // x = y = 3 (mod 4)
            J = -J;
        }
        var t = new BigInteger();
        t = x; x = y; y = t; // switch x and y
    }
    return J;
}

/*
 * Compute a square root of v (mod p)
 * @param {BigInteger} v
 * @returns a square root of v (mod p) if one exists, or null otherwise
 */

function sqrt(v) {
    if (v.signum() === 0) {
        return this._0; // si sqrt(x^3+b) < 0
    }
    if (this.p.testBit(1)) {
        var r = v.modPow(this.p.shiftRight(2).add(this._1), this.p);
        return r.multiply(r).subtract(v).mod(this.p).signum() === 0 ? r : null;
    }
    if (this.p.testBit(2)) {
        var twog = v.shiftLeft(1).mod(this.p);
        var gamma = twog.modPow(this.p.shiftRight(3), this.p);
        var i = twog.multiply(gamma).multiply(gamma).mod(this.p);
        var r = v.multiply(gamma).multiply(i.subtract(this._1)).mod(this.p);
        return r.multiply(r).subtract(v).mod(this.p).signum() === 0 ? r : null;
    }
    if (this.p.testBit(3)) {
        var twou = this.p.shiftRight(2); // (p-1)/4
        var s0 = v.shiftLeft(1).modPow(twou, this.p); // (2v)^(2u) mod p
        var s = s0;
        var d = this._1;
        var fouru = twou.shiftLeft(1);
        while (s.add(this._1).compareTo(this.p) !== 0) {
            d = d.add(this._2);
            s = d.modPow(fouru, this.p).multiply(s0).mod(this.p);
        }
        var w = d.multiply(d).multiply(v).shiftLeft(1).mod(this.p);
        var z = w.modPow(this.p.shiftRight(4), this.p); // w^((p-9)/16)
        var i = z.multiply(z).multiply(w).mod(this.p);
        var r = z.multiply(d).multiply(v).multiply(i.subtract(this._1)).mod(this.p);
        return r.multiply(r).subtract(v).mod(this.p).signum() === 0 ? r : null;
    }
    if (this.p.testBit(4)) {
        var twou = this.p.shiftRight(3); // (p-1)/8
        var s0 = v.shiftLeft(1).modPow(twou, this.p); // (2v)^(2u) mod p
        var s = s0;
        var d = this._1;
        var fouru = twou.shiftLeft(1); // (p-1)/4
        while (s.add(this._1).compareTo(this.p) !== 0) {
            d = d.add(this._2);
            s = d.modPow(fouru, this.p).multiply(s0).mod(this.p);
        }
        var w = d.multiply(d).multiply(v).shiftLeft(1).mod(this.p);
        var z = w.modPow(this.p.shiftRight(5), this.p); // w^((p-17)/32)
        var i = z.multiply(z).multiply(w).mod(this.p);
        var r = z.multiply(d).multiply(v).multiply(i.subtract(this._1)).mod(this.p);
        return r.multiply(r).subtract(v).mod(this.p).signum() === 0 ? r : null;
    }
    if (v.compareTo(this._4) === 0) {
        return this._2;
    }
    var z = v.subtract(this._4).mod(this.p);
    var t = this._1;
    while (this.legendre(z) >= 0) {
        t = t.add(this._1);
        z = v.multiply(t).multiply(t).subtract(this._4).mod(this.p);
    }
    var z = v.multiply(t).multiply(t).subtract(this._2).mod(this.p);
    var r = this.lucas(z, this.p.shiftRight(2)).multiply(t.modInverse(this.p)).mod(this.p);
    return r.multiply(r).subtract(v).mod(this.p).signum() === 0 ? r : null;
}

JSParams.prototype.legendre = legendre;
JSParams.prototype.sqrt = sqrt;